﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;

namespace com.intellitack.layar.library
{
    /// <summary>
    /// KMLHelper Class
    /// Extracts information from a KML file and returns a list of Layar PointOfInterest's
    /// </summary>
    public class KMLHelper
    {
        // constructor
        public KMLHelper() { }

        const Double EARTH_RADIUS_IN_KM = 6366.707019;
        //helper method to make reading the lambda a bit easier
        static double ToRadian(double val)
        {
            return val * (Math.PI / 180);
        }
        //helper method for converting Radians, making the lamda easier to read
        static double DiffRadian(double val1, double val2)
        {
            return ToRadian(val2) - ToRadian(val1);
        }
        /// <summary>
        /// Function to evaluate the distance between two points on the Earth
        /// inspired by: http://blog.wekeroad.com/2007/08/30/linq-and-geocoding/
        /// </summary>
        static double CalculateDistanceFrom(double fromLat, double fromLon, double toLat, double toLong)
        {


            Func<double, double, double, double, double> CalcDistance = (lat1, lon1, lat2, lon2) =>
            EARTH_RADIUS_IN_KM * 2 *
            (
                Math.Asin(
                    Math.Min(1,
                        Math.Sqrt(
                            (
                                Math.Pow(Math.Sin((DiffRadian(lat1, lat2)) / 2.0), 2.0) +
                                Math.Cos(ToRadian(lat1)) * Math.Cos(ToRadian(lat2)) *
                                Math.Pow(Math.Sin((DiffRadian(lon1, lon2)) / 2.0), 2.0)
                            )
                       )
                   )
               )
             )
            ;
            return CalcDistance(fromLat, fromLon, toLat, toLong);
        }

        public static IEnumerable<PointOfInterest> GetGeoList(LayarRequest request, string kml)
        {
            StreamReader sr = new StreamReader(kml);
            string geoDataXml = sr.ReadToEnd();
            sr.Close();

            // Remove the XML namespaces. This is needed to use LINQ to XML
            geoDataXml = geoDataXml.Replace(@"<kml xmlns=""http://earth.google.com/kml/2.2"">", "");
            geoDataXml = geoDataXml.Replace(@"<kml xmlns=""http://earth.google.com/kml/2.1"">", "");
            geoDataXml = geoDataXml.Replace("</kml>", "");
            geoDataXml = geoDataXml.Replace(@"xmlns=""urn:oasis:names:tc:ciq:xsdschema:xAL:2.0""", "");
            XDocument xDoc = XDocument.Parse(geoDataXml);

            // Google Maps always returns floats with a dot as decimal separator
            NumberFormatInfo provider = new NumberFormatInfo();
            provider.NumberDecimalSeparator = ".";

            // LINQ to XML query to parse XML file and return list of placemarks
            // Use the XDocumentHelper class to try to get element and attribute values
            // If no element or attribute is not found, the GetValue() method will return an empty string
            var geoList =
              (from item in xDoc.Descendants("Placemark")
               let coordinates = XDocumentHelper.GetValue(item, "Point/coordinates", "")
               let distance = CalculateDistanceFrom(Convert.ToDouble(request.lat, provider), Convert.ToDouble(request.lon, provider), Convert.ToDouble(coordinates.Split(',')[1], provider), Convert.ToDouble(coordinates.Split(',')[0], provider))
               orderby distance ascending
               where distance < (request.radius + request.accuracy) / 1000
               select new PointOfInterest()
               {
                   title = XDocumentHelper.GetValue(item, "name", ""),
                   lat = Convert.ToDecimal(coordinates.Split(',')[1], provider),
                   lon = Convert.ToDecimal(coordinates.Split(',')[0], provider),
                   distance = Convert.ToDecimal(distance, provider),
                   type = 1,
                   /*
                    * example of how to extract information from your custom KML description field
                    *
                   imageURL = RegexMatch("<img[^>]* src=\"([^\"]*)\"[^>]*>", XDocumentHelper.GetValue(item,
                     "description", ""), 1),
                   actions = { (new POIAction(RegexMatch("<a[^>]* href=\"([^\"]*)\"[^>]*>", XDocumentHelper.GetValue(item,
                     "description", ""), 3), "details (site)"))},

                   line2 = RegexMatch("<font color=\"#000\" face=\"Trebuchet MS\">([a-zA-Z\\s_-]*)</font>", XDocumentHelper.GetValue(item,
                     "description", ""), 0),
                   line3 = RegexMatch("<font color=\"#65d500\" face=\"Trebuchet MS\" size=\"4\"><b><span style=\"text-decoration: none;\">(.*)</span>", XDocumentHelper.GetValue(item,
                   "description", ""), 0),
                   line4 = RegexMatch("<font color=\"#000\" face=\"Trebuchet MS\">(.*)</font>", XDocumentHelper.GetValue(item,
                     "description", ""), 1),
                   attribution = RegexMatch("<font color=\"#000\" face=\"Trebuchet MS\">(.*)</font></a></td>", XDocumentHelper.GetValue(item,
                     "description", ""), 1),
                   */
               }).Take(70); // Layar gets unstable when returning too many results so limit to 70 here

            return geoList;
        }
        /// <summary>
        /// XDocument Helper class
        /// Pass path to elements as string and get element or attribute value
        /// </summary>
        public static class XDocumentHelper
        {
            public static string GetValue(XElement parentElement, string xmlElementNames, string xmlAttributeName)
            {
                string[] elementNames = xmlElementNames.Split('/');
                XElement element = parentElement;

                foreach (string name in elementNames.Where(n => n != ""))
                {
                    element = element.Element(name);
                    if (element == null)
                    {
                        return "";
                    }
                }

                if (string.IsNullOrEmpty(xmlAttributeName))
                {
                    return element.Value;
                }

                XAttribute attr = element.Attribute(xmlAttributeName);
                if (attr == null)
                {
                    return "";
                }
                return attr.Value;
            }

        }
        /// <summary>
        /// RegexMatch class
        /// Helper class to extract information from KML description field
        /// </summary>
        public static string RegexMatch(string Regex, string Data, short matchid)
        {
            Regex regex = new Regex(Regex, RegexOptions.Compiled | RegexOptions.CultureInvariant);
            MatchCollection matches = regex.Matches(Data);
            if (matches.Count > matchid)
                return matches[matchid].Groups[1].Value;
            else
                return "";
        }

    }
}
